今天將會使用 Geth 與 puppeth 來建立私有鏈
並且在上面佈署投票合約做測試
brew tap ethereum/ethereum
brew install ethereum
確認安裝完成
geth version
這裡需要建立兩個結點
建立兩個資料夾 private-chain-node1, private-chain-node2
mkdir private-chain-node1 private-chain-node2
執行以下指令
geth --datadir private-chain-node1 account new
geth --datadir private-chain-node2 account new
這邊需要個別紀錄所產生出來的 address 做後面使用
由於兩個結點的初始區塊是一致的
所以一起使用 puppeth 設定
執行 puppeth
puppeth
會出現一連串設定選擇
選擇配置新的 genesis
What would you like to do? (default = stats)
1. Show network stats
2. Configure new genesis
3. Track new remote server
4. Deploy network components
> 2
選擇 PoA 只有被授權的節點才可以寫入資料
Which consensus engine to use? (default = clique)
1. Ethash - proof-of-work
2. Clique - proof-of-authority
> 2
產生一個 Block 的時間, 選擇預設值 15
How many seconds should blocks take? (default = 15)
> 15
哪些結點有打包 Block 的權利,填入前面的 address
Which accounts are allowed to seal? (mandatory at least one)
> 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
>
設定哪些節點的帳戶預設就有ETH(以太幣),填入前面的 address
Which accounts should be pre-funded? (advisable at least one)
> 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
>
設定私有鏈的 chainID
Specify your chain/network ID if you want an explicit one (default = random)
> 123456
最後選擇匯出成 gensis.json
geth --datadir private-chain-node1 init genesis.json
geth --datadir private-chain-node2 init genesis.json
然後個別進入兩個資料夾 private-chain-node1, private-chain-node2
以不同的 port 與 authrpc.port 來執行兩個結點
在 private-chain-node1 執行
geth --datadir ./ --networkid 123456 --port 8000 --nodiscover console
則 private-chain-node2 執行
geth --datadir ./ --networkid 123456 --port 8001 --authrpc.port=8546 --nodiscover console
在 private-chain-node1 執行
node.adminInfo.enode
查出 enode 資訊
在到 private-chain-node2 執行以下指令把 private-chain-node1 加入 peer
admin.addPeer("enode://5990e88cfc948c87a10fa85a98808cc0e46f1a353620f3349fb642f4dce7953b9298174d7043164a556ba9383f29222425c66ac0516a17be3003167427da6f30@127.0.0.1:8000?discport=0")
透過以下指令可以查到連結後的 peer 資訊
admin.peers
yarn global add truffle
mkdir vote_dapp
cd vote_dapp
truffle unbox webpack
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Voting {
mapping (bytes32 => uint8) public votesReceived;
bytes32[] public candidateList;
constructor (bytes32[] memory candidateNames) {
candidateList = candidateNames;
}
function totalVotesFor(bytes32 candidate) public view returns (uint8) {
require(validCandidate(candidate));
return votesReceived[candidate];
}
function voteForCandidate(bytes32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
function validCandidate(bytes32 candidate) view public returns (bool) {
for(uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
var Voting = artifacts.require("Voting");
const web3 = require("web3");
module.exports = function(deployer) {
deployer.deploy(Voting, [web3.utils.asciiToHex('Rama'), web3.utils.asciiToHex('Nick'), web3.utils.asciiToHex('Jose')]);
};
新增 development 設定到 networks
development: {
host: '127.0.0.1',
port: 8545,
network_id: '123456'
},
修改 complier solc版本為 Contract 指定版本 0.8.0
compilers: {
solc: {
version: "0.8.0"
}
}
透過以下指令執行佈署
truffle migration --network development
備註 為了要讓 geth 可以接收以 http rpc 的 transaction 發送需要修改啟動指令如下
private-chain-node1
geth --datadir ./ --networkid 123456 --port 8000 --rpc.allow-unprotected-txs --http --http.crossdomain="*" --allow-insecure-unlock --nodiscover console
private-chain-node2
geth --datadir ./ --networkid 123456 --port 8001 --rpc.allow-unprotected-txs --http --http.port=8547 --authrpc.port=8546 --http.crossdomain="*" --allow-insecure-unlock --nodiscover console
// Import libraries we need.
import { default as Web3 } from 'web3';
import { default as contract } from 'truffle-contract'
import voting_artifacts from '../../build/contracts/Voting.json'
var Voting = contract(voting_artifacts);
let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
window.voteForCandidate = function(candidate) {
let candidateName = $("#candidate").val();
try {
$("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.")
$("#candidate").val("");
Voting.deployed().then(async function(contractInstance) {
let accounts = await web3.eth.getAccounts();
contractInstance.voteForCandidate(Web3.utils.asciiToHex(candidateName), {gas: 140000, from: accounts[0]}).then(function() {
let div_id = candidates[candidateName];
return contractInstance.totalVotesFor.call(Web3.utils.asciiToHex(candidateName)).then(function(v) {
$("#" + div_id).html(v.toString());
$("#msg").html("");
});
});
});
} catch (err) {
console.log(err);
}
}
$( document ).ready(function() {
// 設定連結 RPC
window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));
Voting.setProvider(web3.currentProvider);
let candidateNames = Object.keys(candidates);
for (var i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i];
Voting.deployed().then(function(contractInstance) {
contractInstance.totalVotesFor.call(Web3.utils.asciiToHex(name)).then(function(v) {
$("#" + candidates[name]).html(v.toString());
});
})
}
});
<!DOCTYPE html>
<html>
<head>
<title>Vote DApp</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>
<body class="container">
<h1>A Simple Hello World Voting Application</h1>
<div id="address"></div>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Candidate</th>
<th>Votes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Rama</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Nick</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>Jose</td>
<td id="candidate-3"></td>
</tr>
</tbody>
</table>
<div id="msg"></div>
</div>
<input type="text" id="candidate" />
<a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.8.0/web3.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="index.js"></script>
</html>
這邊為了方便所以直接使用 RPC 內部的 account
為了讓帳戶公平所以應該限制每個 account 只能投一票
並且允許使用 MetaMask